package com.illumination.monitor.common.utils;

import lombok.extern.slf4j.Slf4j;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 对象比较器
 */
@Slf4j
public class Comparer {

     /**
     * 注解: 需要的忽略的属性
     */
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface IgnoreCompare {
    }

    public interface Identify {
        /**
         * 返回当前类的唯一标识
         *
         * @return
         */
        String key();

    }

    /**
     * 不支持List<List<Object>>
     * 不支持List<Map<?>>
     *
     * @param source
     * @param target
     * @return
     * @throws Exception
     */
    public static boolean isEquals(Object source, Object target) throws Exception {
        if (source instanceof Collection) {
            if (isEqualColl(source, target) == false) {
                return false;
            }
        } else if (source instanceof Map) {
            if (isEqualMap(source, target) == false) {
                return false;
            }
        } else if (Comparer.isEqualObject(source, target) == false) {
            return false;
        }
        return true;
    }


    /**
     * 两个对象内容 包含集合属性(集合泛型的类必须实现Identify接口实现key方法) 是否相同
     *
     * @return
     */
    protected static boolean isEqualObject(Object source, Object target) throws Exception {
        if (source == target) return true;
        if ((source == null && target != null) || (source != null && target == null)) return false;
        Class<?> sourceClass = source.getClass();
        Field[] fields = sourceClass.getDeclaredFields();
        for (Field field : fields) {
            IgnoreCompare annotation = field.getAnnotation(IgnoreCompare.class);
            if (annotation != null) continue;
            field.setAccessible(true);
            Object sourcevalue = field.get(source);
            Object targetvalue = field.get(target);
            if (sourcevalue == targetvalue) continue;
            if ((sourcevalue == null && targetvalue != null) || (sourcevalue != null && targetvalue == null)) {
                log.info("{}.{} not equal,{}!={}", sourceClass.getName(), field.getName(), sourcevalue, targetvalue);
                return false;
            }
            if (sourcevalue instanceof Collection) {
                if (isEqualColl(sourcevalue, targetvalue) == false) {
                    return false;
                }
            } else if (sourcevalue instanceof Map) {
                if (isEqualMap(sourcevalue, targetvalue) == false) {
                    return false;
                }
            } else {
                ClassLoader loader = sourcevalue.getClass().getClassLoader();
                if (loader == null) {
                    if (!sourcevalue.equals(targetvalue)) {
                        log.info("{}.{} not equal,{}!={}", sourceClass.getName(), field.getName(), sourcevalue, targetvalue);
                        return false;
                    }
                } else {
                    if (Comparer.isEqualObject(sourcevalue, targetvalue) == false) {
                        return false;
                    }
                }
            }
        }
        return true;
    }


    /**
     * 集合比较
     *
     * @param sources
     * @param targets
     * @return
     * @throws Exception
     */
    protected static boolean isEqualColl(Object sources, Object targets) throws Exception {
        if (sources == targets || (((Collection) sources).size() == 0 && ((Collection) targets).size() == 0)) {
            return true;
        }

        if (sources == null || targets == null || ((Collection) sources).size() != ((Collection) targets).size()) {
            return false;
        }

        ClassLoader classLoader = ((Collection<?>) sources).stream().findFirst().get().getClass().getClassLoader();
        if (classLoader == null) {
            Collection sourceCollection = (Collection) sources;
            Collection targetCollection = (Collection) targets;
            Map sourceMap = (Map) sourceCollection.stream().collect(Collectors.toMap(o -> o, Function.identity(), (o1, o2) -> o2));
            Iterator iterator = targetCollection.iterator();
            while (iterator.hasNext()) {
                Object target = iterator.next();
                Object source = sourceMap.get(target);
                if (isEquals(source, target) == false) {
                    return false;
                }
            }
        } else {
            Collection<? extends Identify> sourceCollection = (Collection) sources;
            Collection<? extends Identify> targetCollection = (Collection) targets;
            Map<String, ? extends Identify> sourceMap = sourceCollection.stream().collect(Collectors.toMap(Identify::key, Function.identity(), (k1, k2) -> k2));
            Iterator<? extends Identify> iterator = targetCollection.iterator();
            while (iterator.hasNext()) {
                Identify target = iterator.next();
                Identify source = sourceMap.get(target.key());
                if (Comparer.isEqualObject(source, target) == false) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Map集合比较
     *
     * @param source
     * @param target
     * @return
     * @throws Exception
     */
    protected static boolean isEqualMap(Object source, Object target) throws Exception {
        Map<Object, Object> sourceMap = (Map<Object, Object>) source;
        Map<Object, Object> targetMap = (Map<Object, Object>) target;
        if (sourceMap == targetMap || (sourceMap.size() == 0 && targetMap.size() == 0)) {
            return true;
        }
        if (sourceMap == null || targetMap == null || sourceMap.size() != targetMap.size()) {
            return false;
        }
        Iterator<Map.Entry<Object, Object>> sourceIterator = sourceMap.entrySet().iterator();
        while (sourceIterator.hasNext()) {
            Map.Entry<Object, Object> next = sourceIterator.next();
            Object sourceObject = next.getValue();
            Object targetObject = targetMap.get(next.getKey());
            if (isEquals(sourceObject, targetObject) == false) {
                return false;
            }
        }
        return true;
    }


}
